<?php
include_once $_SERVER['DOCUMENT_ROOT'].'/lib/jObj.php';
include_once $_SERVER['DOCUMENT_ROOT'].'/lib/jRouter.php';

class jApi extends jObj{
  protected static $_instance;
	protected $inited=false;
	protected $incPath='';
	protected $conf='';

	const innerCall=true;
	const httpCall=false;
	
	public $router=false;
	public $DB=false;
	public $lang='ru';

	public function __construct($jApiConfig=false){
	  if(!$this->init($jApiConfig)) return $this->error('init error');
	}
	public static function inst($jApiConfig=false){
	  if( null===self::$_instance ){
	    self::$_instance=new self($jApiConfig);
		}
		return self::$_instance;
	}
	
	//errors
	protected function error($msg,$ret=false){
		if($ret){$this->errors['err'][]=$msg; return false;}
		else $this->throw_error($msg);
	}
	protected function warning($msg,$ret=true){
		if($ret){$this->errors['wrn'][]=$msg; return false;}
		else $this->throw_error($msg);
	}
	
	public function init($jApiConfig){
	  if($this->inited) return true;
	  
	  if(is_string($jApiConfig)){
	    if(!$this->conf=$this->loadConfig($jApiConfig)) return false;
	  }else
			$this->conf=$jApiConfig;


    if(!empty($this->conf['incPath'])) $this->setIncPath($this->conf['incPath']);
	  if(!empty($this->conf['lang'])) $this->setLang($this->conf['lang']);
	  if(!empty($this->conf['dbConf'])) $this->setDBConf($this->conf['dbConf']);

		if(empty($this->conf['router'])) return $this->error('router config not found');
		$this->router=jRouter::inst($this->conf['router']);
		if($this->router->hasErrors())
			return $this->error('Router get error ('.implode('->', $this->router->getErrors()).')' );



	  $this->inited=true;
	  return true;
	}
	
  public function loadConfig($configFile){
    if( empty($configFile) ) return $this->error('passed empty config file string');
    if( !file_exists($configFile) ) return $this->error('config "'.$configFile.'" not found');
    $conf=include_once $configFile;
		if(empty($conf) ) return $this->error('config empty');
		return $conf;
	}
	public function getConfig(){
	  return $this->conf;
	}
	
	public function getClone(){
	  return new jApi( $this->conf );
	}

	public function setIncPath($path){
	  $this->incPath=str_replace('{docRoot}', $_SERVER['DOCUMENT_ROOT'], $path);
	}
	
	public function setLang($ln){
		if(  preg_match('~[A-Za-z]{2}~', $ln) ){
	    $this->lang=mb_strtolower($ln, 'utf8');
		}
	}

	public function setDBConf($dbConf){
	  //if(!file_exists($fileName)) return $this->error('DB config file not found');
		$this->dbConf=$dbConf;
	}
	
	public function getDB(){
	  if($this->DB) return $this->DB;
	  if(!$this->dbConf) return $this->error('DB not configured');
	  include_once $_SERVER['DOCUMENT_ROOT'].'/lib/Db.class.php';
		if(!$this->DB=new DB($this->dbConf)) return $this->error('DB create error');
		return $this->DB;
	}

  public function throw_error($msg,$code=200){
	  header($msg, true, $code);
	  $this->sendAsJson( array('error'=>$msg) );
		exit();
	}
	
	public function run( $retRes=false ){
	  if(!$call=$this->router->run()){
	    $this->throw_error( implode('->', $this->router->getErrors()) );
		}
	  if(!$rs=$this->execute(
			$call,
			jAPI::httpCall
		)){
		  //$this->throw_error( implode('->', $this->getErrors() ) );
		  return $this->error( implode('->', $this->getErrors() ) ,$retRes );
		}

		if($retRes)	return $rs;
		return  $this->sendAsJson($rs);
	}
	
	public function sendAsJson($rs){
	  $rs=json_encode($rs);
    $this->sendJson($rs);
		return true;
	}
	public function sendJson($rs){
    header('Content-Type: application/json');
    header('Content-Length:' . strlen($rs));
		echo $rs;
		return true;
	}

	public function invoke( $url='/', $method='GET', $data=array() ){
	  if(!$call=$this->router->resolve($method,$url,$data)){
	    return $this->error( 'Routing error: ('. implode(' -> ', $this->router->getErrors()) .')' ,true );
		}
	  if(!$rs=$this->execute(
			$call,
			jAPI::innerCall
		)){
		  return false;
		}

		return $rs;
	}
	
	public function get($url){
	  if(!$rs=$this->invoke($url,'GET')){
	    return false;
		}
		return $rs;
	}
	public function post($url,$data){
	  if(!$rs=$this->invoke($url,'POST',$data)){
	    return false;
		}
		return $rs;
	}

	protected function execute($call, $innerCall=jAPI::httpCall ){

	  if(empty($call)) return $this->error('empty call passed');
	  if(empty($call['controller']['class']) && empty($call['controller']['function']) ) return $this->error('expected class or function detection');
	  if(empty($call['controller']['path']) && empty($call['controller']['class'])) return $this->error('expected path or class detection');
	  
	  if(empty($call['controller']['path'])) $call['controller']['path']=$this->incPath.$call['controller']['class'].'.php';
  	if( !file_exists( $call['controller']['path'] ) ) return $this->error('Controller path "'.$call['controller']['path'].'" not found');
		include_once( $call['controller']['path'] );

	  $args=$call['arguments'];
	  if(!empty($call['options'])) $args[]=$call['options'];
		$this->env=$call['environment'];

		if(!empty($call['controller']['method'])){
		  if( empty($call['controller']['class']) ) return $this->error('expected class detection');
		  if( !class_exists($call['controller']['class']) ) return $this->error('expected class defenition for "'.$call['controller']['class'].'"');

		  //if( is_callable(array($call['controller']['class'],'__construct')) ) $obj=new $call['controller']['class'];
		  //else $obj=$call['controller']['class']::inst();
		  try{
				$obj=new $call['controller']['class']( $this );
			}catch(Exception $e){
				try{
					$obj=$call['controller']['class']::inst( $this );
				}catch(Exception $e){
          return $this->error('fail create controller object');
				}
			}

		  if( !is_callable( array($obj,$call['controller']['method']) ) )
				return $this->error('cant call "'.$call['controller']['class'].'.'.$call['controller']['method'].'" method');
				
			$this->env['innerCall']=$innerCall;
		  $response = call_user_func_array(array($obj,$call['controller']['method']), $args);
		}elseif(!empty($call['controller']['function'])){
		  $response = call_user_func_array($call['controller']['function'], $args);
		}

		if( $obj->hasErrors() ) $this->error( implode('->', $obj->getErrors()) ,$innerCall );
    return $response;
	}
}
?>